// Header do Ogre
#include <Ogre.h>
#include <ExampleApplication.h>

/*
Exemplo que usa ManualObjects para criar um efeito mais bonito e menos custoso para placa de video
implementar estrelas
*/

// Classe que Implementa Randomicos
class Random{
public:
    Random()
    {
    }

    double randomUniform(double min, double max)
    {
        double r;
        r=(double)rand()/RAND_MAX;
        r=min+r*(max-min);
        return r;
    }

    double randomNormal(double mean, double std)
    {
        double x1, x2, w, y1;
        do {
            x1 = 2.0 * randomUniform(0,1) - 1.0;
            x2 = 2.0 * randomUniform(0,1) - 1.0;
            w = x1 * x1 + x2 * x2;
        } while ( w >= 1.0);
        w = sqrt( (-2.0 * log( w ) ) / w );
        y1 = x1 * w;
        y1 = (y1 * std) + mean;
        return y1;
    }

    void randomSphere(double radius=80)
    {
        double l=0;
        for(int j=0;j<3; j++){
            Tabela[j] = randomNormal(0,1);
            l += Tabela[j]*Tabela[j];
        }
        l = sqrt(l);
        double r = randomUniform(0,1);
        r = pow( r, 1/3);
        for(int j=0;j<3; j++){
            Tabela[j] = radius*(r * Tabela[j] / l);
        }
    }

    double PegaTabela(int i = 0) { return Tabela[i]; }

private:
    double Tabela [3];
};


class Starfield{
public:
    Starfield()
    {
    }

    void CreateStarfield(Ogre::SceneManager* mSceneMgr, int NumberOfStars = 6000)
    {
        // Cria uma mesh manualmente
        ManualObject* manual = mSceneMgr->createManualObject("manual");

        manual->begin("BaseWhiteNoLighting", RenderOperation::OT_POINT_LIST);
        float brillance=0.0;
        Random p;
        for (int i=0; i<NumberOfStars; i++)
        {
            // Tamanho da esfera
            p.randomSphere(10000);
            brillance = (int)((float)rand()/32767*99);
            brillance = brillance/100;           
            manual->position(p.PegaTabela(0), p.PegaTabela(1), p.PegaTabela(2));
            manual->colour(ColourValue(brillance, brillance, brillance, 1));
        }
        manual->end();

        // Agora cria um node e anexa a mesh manual criada
        mSceneMgr->getRootSceneNode()->createChildSceneNode()->attachObject(manual);
    }
};

class Planet {
public:
    Planet () { };

    void createSphere(const std::string& strName, const float r, const int nRings = 16, const int nSegments = 16)
    {
        MeshPtr pSphere = 
            MeshManager::getSingleton().createManual(strName, ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
        SubMesh *pSphereVertex = pSphere->createSubMesh();

        pSphere->sharedVertexData = new VertexData();
        VertexData* vertexData = pSphere->sharedVertexData;

        // define the vertex format
        VertexDeclaration* vertexDecl = vertexData->vertexDeclaration;
        size_t currOffset = 0;
        // positions
        vertexDecl->addElement(0, currOffset, VET_FLOAT3, VES_POSITION);
        currOffset += VertexElement::getTypeSize(VET_FLOAT3);
        // normals
        vertexDecl->addElement(0, currOffset, VET_FLOAT3, VES_NORMAL);
        currOffset += VertexElement::getTypeSize(VET_FLOAT3);
        // two dimensional texture coordinates
        vertexDecl->addElement(0, currOffset, VET_FLOAT2, VES_TEXTURE_COORDINATES, 0);
        currOffset += VertexElement::getTypeSize(VET_FLOAT2);

        // allocate the vertex buffer
        vertexData->vertexCount = (nRings + 1) * (nSegments+1);
        HardwareVertexBufferSharedPtr vBuf = HardwareBufferManager::getSingleton().createVertexBuffer(vertexDecl->getVertexSize(0), vertexData->vertexCount, HardwareBuffer::HBU_STATIC_WRITE_ONLY, false);
        VertexBufferBinding* binding = vertexData->vertexBufferBinding;
        binding->setBinding(0, vBuf);
        float* pVertex = static_cast<float*>(vBuf->lock(HardwareBuffer::HBL_DISCARD));

        // allocate index buffer
        pSphereVertex->indexData->indexCount = 6 * nRings * (nSegments + 1);
        pSphereVertex->indexData->indexBuffer = HardwareBufferManager::getSingleton().createIndexBuffer(HardwareIndexBuffer::IT_16BIT, pSphereVertex->indexData->indexCount, HardwareBuffer::HBU_STATIC_WRITE_ONLY, false);
        HardwareIndexBufferSharedPtr iBuf = pSphereVertex->indexData->indexBuffer;
        unsigned short* pIndices = static_cast<unsigned short*>(iBuf->lock(HardwareBuffer::HBL_DISCARD));

        float fDeltaRingAngle = (Math::PI / nRings);
        float fDeltaSegAngle = (2 * Math::PI / nSegments);
        unsigned short wVerticeIndex = 0 ;

        // Generate the group of rings for the sphere
        for( int ring = 0; ring <= nRings; ring++ ) {
            float r0 = r * sinf (ring * fDeltaRingAngle);
            float y0 = r * cosf (ring * fDeltaRingAngle);

            // Generate the group of segments for the current ring
            for(int seg = 0; seg <= nSegments; seg++) {
                float x0 = r0 * sinf(seg * fDeltaSegAngle);
                float z0 = r0 * cosf(seg * fDeltaSegAngle);

                // Add one vertex to the strip which makes up the sphere
                *pVertex++ = x0;
                *pVertex++ = y0;
                *pVertex++ = z0;

                Vector3 vNormal = Vector3(x0, y0, z0).normalisedCopy();
                *pVertex++ = vNormal.x;
                *pVertex++ = vNormal.y;
                *pVertex++ = vNormal.z;

                *pVertex++ = (float) seg / (float) nSegments;
                *pVertex++ = (float) ring / (float) nRings;

                if (ring != nRings) {
                    // each vertex (except the last) has six indices pointing to it
                    *pIndices++ = wVerticeIndex + nSegments + 1;
                    *pIndices++ = wVerticeIndex;               
                    *pIndices++ = wVerticeIndex + nSegments;
                    *pIndices++ = wVerticeIndex + nSegments + 1;
                    *pIndices++ = wVerticeIndex + 1;
                    *pIndices++ = wVerticeIndex;
                    wVerticeIndex ++;
                }
            }; // end for seg
        } // end for ring

        // Unlock
        vBuf->unlock();
        iBuf->unlock();
        // Generate face list
        pSphereVertex->useSharedVertices = true;

        // the original code was missing this line:
        pSphere->_setBounds( AxisAlignedBox( Vector3(-r, -r, -r), Vector3(r, r, r) ), false );
        pSphere->_setBoundingSphereRadius(r);
        // this line makes clear the mesh is loaded (avoids memory leaks)
        pSphere->load();
    };
};

class SampleApp : public ExampleApplication
{
public:   
    SampleApp()
    {}

protected:

    // Sobrecarrega do CreateScene
    void createScene(void)
    {
        Planet * planet = new Planet ();

        mSceneMgr->setAmbientLight( ColourValue( 1,1,1 ) );
        planet->createSphere("mySphereMesh", 100, 64, 64);
        Entity* sphereEntity = mSceneMgr->createEntity("mySphereEntity", "mySphereMesh");
        SceneNode* sphereNode = mSceneMgr->getRootSceneNode()->createChildSceneNode();
        sphereEntity->setMaterialName("Examples/Planet");
        sphereNode->attachObject(sphereEntity);

        /*
        const float width = 25;
        const float height = 25;
        ManualObject mo("moStar");

        //Vector3 vec = Vector3::ZERO;
        Vector3 vec(width/2, 0, 0);

        mo.begin("Examples/Flare", RenderOperation::OT_TRIANGLE_LIST);
        mo.position(-vec.x, height, -vec.z);
        mo.textureCoord(0, 0);

        mo.position(vec.x, height, vec.z);
        mo.textureCoord(1, 0);

        mo.position(-vec.x, 0, -vec.z);
        mo.textureCoord(0, 1);

        mo.position(vec.x, 0, vec.z);
        mo.textureCoord(1, 1);

        mo.triangle(0, 3, 1);
        mo.triangle(0, 2, 3);

        mo.end();

        mo.convertToMesh("GrassBladesMesh");

        Entity *grass = mSceneMgr->createEntity("grass", "GrassBladesMesh");
        StaticGeometry *sg = mSceneMgr->createStaticGeometry("GrassArea");

        const int size = 3750;
        const int amount = 3;

        sg->setRegionDimensions(Vector3(size, size, size));
        sg->setOrigin(Vector3(-size/2, 0, -size/2));

        for (int x = -size/2; x < size/2; x += (size/amount)) {
        for (int y = -size/2; y < size/2; y += (size/amount)) {
        for (int z = -size/2; z < size/2; z += (size/amount))
        {
        Real r = size / (float)amount / 2;
        Vector3 pos(x + Math::RangeRandom(-r, r), 
        y + Math::RangeRandom(-r, r), z + Math::RangeRandom(-r, r));
        //Vector3 scale(1, 1, 1);
        Vector3 scale(1, Math::RangeRandom(0.9, 1.1), 1);

        //Quaternion orientation = pos.getRotationTo(vec);
        Quaternion orientation;
        orientation.FromAngleAxis(Degree(Math::RangeRandom(0, 359)), Vector3::UNIT_Y);

        sg->addEntity(grass, pos, orientation, scale);
        }
        }
        }

        sg->build();

        */
        /*
        ManualObject* manObj = mSceneMgr->createManualObject("manual");
        manObj->setDynamic(true);
        manObj->begin("SomeSimpleMaterial", RenderOperation::OT_POINT_LIST);

        const int size = 375;
        const int amount = 20;

        for (int x = -size/2; x < size/2; x += (size/amount)) {
        for (int y = -size/2; y < size / 2; y += (size / amount)) {
        for (int z = -size/2; z < size/2; z += (size/amount)) {
        Real r = size / (float)amount / 2;
        manObj->position (x + Math::RangeRandom(-r,r),
        y + Math::RangeRandom (-r, r), z + Math::RangeRandom(-r, r));
        }
        }
        }

        //manObj->position(0.0, 0.0, 0.0);
        manObj->end();
        manObj->convertToMesh("StarMesh");
        */

        //const int size = 375;
        //const int amount = 20;

        //Entity *star = mSceneMgr->createEntity("star", "StarMesh");

        //StaticGeometry *sg = mSceneMgr->createStaticGeometry("StarField");
        //sg->setRegionDimensions(Vector3(size, size, size));
        //sg->setOrigin(Vector3(-size/2, 0, -size/2));

        //for (int x = -size/2; x < size/2; x += (size/amount)) {
        //   for (int z = -size/2; z < size/2; z += (size/amount)) {
        //       Real r = size / (float)amount / 2;
        //       Vector3 pos(x + Math::RangeRandom(-r, r), 0, z + Math::RangeRandom(-r, r));
        //       Vector3 scale(1, 1, 1);
        //       Quaternion orientation;
        //       orientation.FromAngleAxis(Degree(Math::RangeRandom(0, 359)), Vector3::UNIT_Y);

        //       sg->addEntity(star, pos, orientation, scale);
        //   }
        //}
        //sg->build();


        //mSceneMgr->getRootSceneNode()->createChildSceneNode()->attachObject(manObj);


        mCamera->setPosition(0, 0, 400);
        mCamera->lookAt(sphereNode->getPosition());
        mCamera->setNearClipDistance(0.5);
        mCamera->setFarClipDistance(1000000);

        /*
        /// Cria o campo estelar
        Starfield *p_Starfield = new Starfield();
        p_Starfield->CreateStarfield(mSceneMgr);      

        //mSceneMgr->setSkyDome(true, "StarTrek/NebulaTeste", 5, 1);//5,1      
        //mSceneMgr->setSkyBox(true, "StarTrek/NebulaTeste");


        // Luz
        mSceneMgr->setAmbientLight( ColourValue( 1, 1, 1 ) );
        Light *light = mSceneMgr->createLight("Light_BOP");
        light->setType(Light::LT_POINT);       
        light->setPosition(Vector3(0, 20, 0));
        light->setDiffuseColour(1.0, 1.0, 1.0);
        light->setSpecularColour(1.0, 1.0, 1.0);

        light = mSceneMgr->createLight("LightGalaxy");
        light->setType(Light::LT_POINT);       
        light->setPosition(Vector3(50, 40, 100));
        light->setDiffuseColour(1.0, 1.0, 1.0);
        light->setSpecularColour(1.0, 1.0, 1.0);

        light = mSceneMgr->createLight("Light3");
        light->setType(Light::LT_DIRECTIONAL);
        light->setDiffuseColour(ColourValue(.25, .25, 0));
        light->setSpecularColour(ColourValue(.25, .25, 0));

        light->setDirection(Vector3( 0, -1, 1 ));

        // Nave Klingon
        //Entity *ent2 = mSceneMgr->createEntity( "Nave_K", "BirdOfPrey.mesh" );       
        //SceneNode *node2 = mSceneMgr->getRootSceneNode()->createChildSceneNode( "Node_Nave_K" );
        //node2->attachObject( ent2 );

        // Nave Enterprise Galaxy
        //Entity *ent3 = mSceneMgr->createEntity( "Nave_Galaxy", "Nave_Enterrpise.mesh" );       
        //SceneNode *node3 = mSceneMgr->getRootSceneNode()->createChildSceneNode( "Node_Nave_Galaxy",Vector3( 50, 20, 100 ) );
        //node3->attachObject( ent3 );
        */
    }
};


// ----------------------------------------------------------------------------
// Main function, just boots the application object
// ----------------------------------------------------------------------------
#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
#define WIN32_LEAN_AND_MEAN
#include "windows.h"
INT WINAPI WinMain( HINSTANCE hInst, HINSTANCE, LPSTR strCmdLine, INT )
#else
int main(int argc, char **argv)
#endif
{
    // Create application object
    SampleApp app;

    try
    {
        app.go();
    }
    catch( Exception& e )
    {
        //#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
        //        MessageBox( NULL, e.getFullDescription().c_str(), "An exception has occured!", MB_OK | MB_ICONERROR | MB_TASKMODAL);
        //#else

        std::cerr << "An exception has occured: " << e.getFullDescription();
        //#endif
    }

    return 0;
}